package com.shang.core.base;

import lombok.AccessLevel;
import lombok.Getter;

import javax.xml.bind.DatatypeConverter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 域（可包含附加位）
 * <p>
 * Created by shangwei2009@hotmail.com on 2021/9/7 14:18
 */
public abstract class Value {

    private static final Pattern HEX = Pattern.compile("^[0-9a-fA-F]+$");

    @Getter
    private final String fieldName;

    /**
     * 域描述
     */
    @Getter
    private final String description;

    @Getter(AccessLevel.PROTECTED)
    private final Meta meta;

    @Getter
    private final LengthType lengthType;

    /**
     * 域字符集，默认ASCII
     */
    protected final Charset charset;

    protected final PadPosition padPosition;

    protected final String paddingHex;

    private final LinkedHashMap<String, Value> children = new LinkedHashMap<>();

    protected Value(Meta meta, Charset charset, PadPosition padPosition, String paddingHex) {
        if (meta == null) {
            throw new CodecException("meta不能为空");
        }
        this.meta = meta;
        this.fieldName = meta.getFieldName();
        this.description = meta.getDescription();
        this.lengthType = meta.getLengthType();
        if (charset == null) {
            throw new CodecException(fieldName + "的域字符集不能为空");
        }
        this.charset = charset;
        if (padPosition == null) {
            throw new CodecException(fieldName + "的域补齐字符位置不能为空");
        }
        this.padPosition = padPosition;
        if (paddingHex == null) {
            throw new CodecException(fieldName + "的域补齐Hex字符不能为空");
        }
        if (!paddingHex.isEmpty()) {
            if (!HEX.matcher(paddingHex).matches()) {
                throw new CodecException(fieldName + "的域补齐字符不是Hex字符");
            }
        }
        this.paddingHex = paddingHex;
    }

    public final Wrapper decode(String hexStr) {
        if (hexStr == null) {
            throw new CodecException(fieldName + "的域为空");
        }
        if (hexStr.length() % 2 != 0) {
            throw new CodecException(fieldName + "的域长度错误");
        }
        try {
            DatatypeConverter.parseHexBinary(hexStr);
        } catch (IllegalArgumentException e) {
            throw new CodecException(fieldName + "遇到非Hex字符串");
        }
        try {
            final Wrapper metaWrapper = meta.decode(hexStr);
            final Wrapper valueWrapper = decodeInternal(hexIncludeMeta() ? hexStr : metaWrapper.getRightHexStr(), metaWrapper.getValueLength());
            decodeChildren(valueWrapper.getHexStr(), valueWrapper.getPlainStr(), valueWrapper.getRightHexStr(), new ArrayList<>(children.values()), valueWrapper.getChildren());
            return valueWrapper.fieldName(fieldName).description(description);
        } catch (StringIndexOutOfBoundsException e) {
            throw new CodecException(fieldName + "的域长度错误");
        } catch (NumberFormatException e) {
            throw new CodecException(fieldName + "的域解析错误");
        }
    }

    /**
     * 顺序添加或覆盖子域
     *
     * @param child 子域
     * @return 本域
     */
    @SuppressWarnings("unchecked")
    public <T extends Value> T child(Value child) {
        if (child == null) {
            throw new CodecException(fieldName + "的子域不能为空");
        }
        children.put(child.fieldName, child);
        return (T) this;
    }

    /**
     * 一般情况下子域的解析，针对的是msg_head包含H001、H002等域的情况，所有子域均来自当前域的Hex字符串
     *
     * @param hexStr      当前Hex串（包含补齐字符）
     * @param plainStr    Value中表示解码后的字符串
     * @param rightHexStr 剩余Hex串
     * @param from        子域配置
     * @param to          子域解析值
     */
    protected void decodeChildren(String hexStr, String plainStr, String rightHexStr, List<Value> from, List<Wrapper> to) {
        Wrapper wrapper = new Wrapper(0, "", "", hexStr);
        for (final Value child : from) {
            wrapper = child.decode(wrapper.getRightHexStr());
            to.add(wrapper);
        }
    }

    /**
     * 域（不包含附加位）解析方法
     *
     * @param hexStr      当前剩余Hex串
     * @param valueLength 定长时表示域字节数（压缩BCD码另算），变长时表示从附加位中解析的域长度（根据实际情况对待）
     * @return {@link Wrapper}
     */
    private Wrapper decodeInternal(String hexStr, Integer valueLength) {
        final Wrapper wrapper = new Wrapper();
        wrapper.setValueLength(valueLength);
        if (lengthType == LengthType.ONE_HEX) {
            // Hex字符数
            final int evenLength = upEven(valueLength);
            if (padPosition == PadPosition.LEFT) {
                // 偏移开头到偶数字符
                wrapper.setPlainStr(hexStr.substring(evenLength - valueLength, evenLength));
            } else if (padPosition == PadPosition.RIGHT) {
                // 开头到末尾
                wrapper.setPlainStr(hexStr.substring(0, valueLength));
            } else if (valueLength == evenLength) {
                // 偶数字符，不用补位
                wrapper.setPlainStr(hexStr.substring(0, valueLength));
            } else {
                // NONE且为奇数字符
                throw new CodecException(fieldName + "没有配置补齐方式");
            }
            wrapper.setHexStr(hexStr.substring(0, evenLength));
            wrapper.setRightHexStr(hexStr.substring(evenLength));
        } else {
            wrapper.setHexStr(hexStr.substring(0, valueLength * 2));
            wrapper.setPlainStr(hexStr.substring(0, valueLength * 2));
            wrapper.setRightHexStr(hexStr.substring(valueLength * 2));
        }
        // 使用plainStr临时存储待解析Hex
        final String mvStr = wrapper.getPlainStr();
        if (mvStr == null) {
            throw new CodecException(fieldName + " valueStr入参为空");
        }
        final String valueStr = valueStr(mvStr);
        if (valueStr == null) {
            throw new CodecException(fieldName + " valueStr返回值为空");
        }
        wrapper.setPlainStr(valueStr);
        return wrapper;
    }

    /**
     * 此函数决定参与解包的域是否包含附加位，默认情况都是不包含的（实际情况如此）
     * <p>
     * 由于这个参数很少改变，故不作为构造函数参数传入
     *
     * @return 待解包的Hex字符串是否包含Meta
     */
    protected boolean hexIncludeMeta() {
        return false;
    }

    /**
     * 域Hex字符串（不包含补齐字符）转换成明文字符串
     *
     * @param valueStr 当前域Hex（可包含附加位，但不包含补齐字符）
     * @return 解析后的字符串
     */
    protected abstract String valueStr(String valueStr);

    private Integer upEven(Integer value) {
        return value + value % 2;
    }
}
